package main

import (
	"encoding/json"
	"io"
	"net"
	"net/http"
	_ "net/http/pprof"
	"strconv"
	"sync"
	"time"

	"server_cluster/common/config"
	"server_cluster/common/logger"
	"server_cluster/common/myepoll"
	"server_cluster/common/mysock"
	"server_cluster/common/protoc/pb"
	"server_cluster/common/sharedb"
	"server_cluster/common/tools"
	"server_cluster/logic/db"
	"server_cluster/logic/game_config"
	"server_cluster/logic/gamelogic"
	"server_cluster/logic/pkg/alg"
	"server_cluster/logic/sharemem"

	"github.com/arl/statsviz"
	"google.golang.org/protobuf/proto"
)

var wg sync.WaitGroup

func handleConn() {

	eps := &myepoll.Eps

	wg.Add(1)
	defer wg.Done()
	mybuf := mysock.Create()
	var result int16
	var myshm = sharemem.MyShm
	var shm_user *sharemem.User

	onlineUserTicker := time.NewTicker(time.Second)
	gateKeepAliveTicker := time.NewTicker(time.Second * 30)
	shmUserTicker := time.NewTicker(time.Second * 60)

	logger.ErrorMsgCallback = gamelogic.LogErrorMsgCallback

	for myshm.Status == 1 || myshm.Status == 2 {
		select {
		case gmCmdReq := <-GmCmdReqChan:
			logger.Info("run gm cmd req: %v", gmCmdReq)
			targetUser, exist := sharemem.GetUserMap()[int64(gmCmdReq.Uid)]
			if !exist {
				logger.Warn("gm cmd req bind uid not online")
				GmCmdRspChan <- &GmCmdRsp{
					Ok:  false,
					Msg: "user not online",
				}
				continue
			}
			gamelogic.GMCmdInst.User = targetUser.Shm_user
			ok, msg := gamelogic.CallGMCmdP(gmCmdReq.FuncName, gmCmdReq.ParamList)
			GmCmdRspChan <- &GmCmdRsp{
				Ok:  ok,
				Msg: msg,
			}
			gamelogic.GMCmdInst.User = nil
		case localEvent := <-gamelogic.LocalEventChan:
			logger.Info("do local event: %v", localEvent)
			gamelogic.LocalEventHandle(localEvent)
		case <-onlineUserTicker.C:
			for key, user := range sharemem.GetUserMap() {
				if user == nil {
					logger.Error("[onlineUserTicker] user is nil! uid: %v", key)
					continue
				}
				if !user.Shm_user.IsOnline {
					continue
				}
				gamelogic.SELF = user.Shm_user
				gamelogic.OnTick(user.Shm_user)
				gamelogic.SELF = nil
			}
		case <-gateKeepAliveTicker.C:
			heartBuf := mysock.Create()
			heartBuf.Command = config.CSC_Heart
			heartData, _ := proto.Marshal(&pb.CSC_Heart{
				Timenow: myshm.TimeNow,
			})
			eps.Fds.Range(func(key, value interface{}) bool {
				conn, ok := value.(*myepoll.Conn)
				if !ok {
					return true
				}
				if conn.Timeout < tools.TimeNow() {
					logger.Warn("gate conn timeout close, addr: %v", conn.Socket.RemoteAddr().String())
					eps.CleanServerConn(conn.Type, conn.Sid)
					return true
				}
				heartBuf.WriteToByConn(conn.Socket, heartData)
				return true
			})
			// logger.Info("[StaticHeapMemory] usage: %.3f%%", float64(sharemem.Heap.GetAllocSize())/float64(sharemem.StaticHeapSize)*100.0)
		case <-shmUserTicker.C:
			gamelogic.CalcUserStatus()
			new(sharedb.Config_server).Update_stock(db.Conn, int16(game_config.GetConfig().ServerId), int32(game_config.USER_MAX-len(sharemem.GetUserMap())))
		default:
			break
		}

		es, err := eps.Wait(100)
		if err != nil {
			continue
		}
		for _, e := range es {
			conn := eps.GetConnByFd(e.Fd)
			// 有消息
			shm_user = nil
			result = int16(mybuf.ReadFrom(conn.Socket))
			if result != config.OK {
				logger.Error("[handleConn] result: %v", result)
				if result == config.TIMEOUT {
					continue
				}
				//if result == config.OVERRUN {
				//	mybuf.Command = config.SC_ERROR
				//	mybuf.Code = config.OVERRUN
				//	mybuf.WriteTo(conn.Socket, nil)
				//	continue
				//}
				logger.Warn("gate conn close, result: %v, addr: %v", result, conn.Socket.RemoteAddr().String())
				eps.CleanServerConn(conn.Type, conn.Sid)
				continue
			}
			conn.Timeout = tools.TimeNow() + config.TIME_OUT_MAX
			if mybuf.Session == 0 {
				continue
			}
			logger.Info("logic read from Gate:Command %d Session=%d Code=%d,Data_length = %d Ext=%d", mybuf.Command, mybuf.Session, mybuf.Code, mybuf.Data_length, mybuf.Ext)
			result = gamelogic.Action(conn, mybuf, &shm_user)
			if result != config.OK {
				mybuf.Command = config.SC_Kick_User
				mybuf.WriteToByConn(conn.Socket, nil)
				logger.Error("server error: %v, addr: %v", result, conn.Socket.RemoteAddr().String())
			}
			if shm_user == nil {
				continue
			}
			if shm_user.Kick {
				mybuf.Command = config.SC_Kick_User
				mybuf.WriteOutByConn(conn.Socket, nil)
			}
		}
	}
}

func listenAccept() {
	listener, err := net.Listen("tcp4", game_config.GetConfig().GameHost)
	if err != nil {
		logger.Error("tcp listen err: %v", err)
		panic(err)
	}

	eps := &myepoll.Eps

	err = eps.InitServer(game_config.USER_MAX)
	if err != nil {
		listener.Close()
		logger.Error("eps err: %v", err)
		panic(err)
	}

	myshm := sharemem.MyShm

	go handleConn()

	logger.Warn("Server started.Listen on %v", game_config.GetConfig().GameHost)
	var nfd int32
	for myshm.Status == 1 {
		// 此处会阻塞
		conn, err := listener.Accept()
		if err != nil {
			continue
		}
		rawConn, err := conn.(*net.TCPConn).SyscallConn()
		if err != nil {
			continue
		}
		rawConn.Control(func(fd uintptr) {
			nfd = int32(fd)
		})
		if myshm.Status != 1 {
			conn.Close()
			break
		}
		sid, err := new(sharedb.Config_server).GetSidByAddr(db.Conn, conn.RemoteAddr().String())
		if err != nil || sid == 0 {
			conn.Close()
			continue
		}
		err = eps.AddFd(nfd, conn.(*net.TCPConn), tools.TimeNow(), myepoll.CONN_TYPE_GATE, int16(sid), 0)
		if err != nil {
			conn.Close()
			continue
		}
		logger.Warn("gate connect, addr: %v, fd: %v", conn.RemoteAddr().String(), nfd)
		heartBuf := mysock.Create()
		heartBuf.Command = config.CSC_Heart
		heartData, _ := proto.Marshal(&pb.CSC_Heart{
			Timenow: myshm.TimeNow,
		})
		heartBuf.WriteToByConn(conn, heartData)
	}
	logger.Warn("listen accept stop ...")
	listener.Close()
}

func main() {
	game_config.InitConfig()

	logger.InitLogger("mserver_"+strconv.Itoa(int(game_config.GetConfig().ServerId)), game_config.GetConfig().LogLevel)
	defer logger.CloseLogger()

	//go func() {
	//	err := RunHttpServer("0.0.0.0:" + strconv.Itoa(int(game_config.GetConfig().HttpPort)))
	//	if err != nil {
	//		logger.Error("run http server err: %v", err)
	//	}
	//}()

	shmid := sharemem.GetShm()
	if shmid <= 0 {
		logger.Error("init myshm fail.")
		panic("init myshm fail.")
	}

	sharemem.InitHeap()

	err := db.InitDB()
	if err != nil {
		logger.Error("init db fail. err: %v", err)
		panic(err)
	}

	sharemem.InitConfigMap()
	sharemem.SnowflakeWorker = alg.NewSnowflakeWorker(int64(game_config.GetConfig().ServerId))
	sharemem.InitUserMap()
	go sharemem.CheckUserMap()

	new(sharedb.Config_server).Update_server_status(db.Conn, int16(game_config.GetConfig().ServerId), 1)
	new(sharedb.Config_server).Update_stock(db.Conn, int16(game_config.GetConfig().ServerId), int32(game_config.USER_MAX-len(sharemem.GetUserMap())))

	var myshm = sharemem.MyShm

	myshm.Status = 1
	myshm.TimeNow = tools.TimeNow() + myshm.TimeOffset
	go listenAccept()

	logger.Warn("server start...")
	ticker := time.NewTicker(time.Second)
	for myshm.Status == 1 {
		<-ticker.C
		myshm.TimeNow = tools.TimeNow() + myshm.TimeOffset
	}
	ticker.Stop()

	// TODO 停服倒计时

	logger.Warn("server stop...status=%d", myshm.Status)
	for i := 3; i >= 0; i-- {
		logger.Warn("%v", i)
		time.Sleep(1 * time.Second)
	}
	myshm.Status = 3
	logger.Warn("main wait...")
	wg.Wait()
	logger.Warn("main stop ...")
	myshm.Status = 0
	new(sharedb.Config_server).Update_server_status(db.Conn, int16(game_config.GetConfig().ServerId), 0)
	gamelogic.SaveAllUser()
	logger.Warn("server stop.")
}

func RunHttpServer(addr string) error {
	// 性能检测
	// 原生pprof /debug/pprof
	// 可视化图表 /debug/statsviz
	err := statsviz.RegisterDefault()
	if err != nil {
		return err
	}
	http.HandleFunc("/gm/cmd", func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "POST" {
			w.WriteHeader(http.StatusMethodNotAllowed)
			_, _ = w.Write([]byte("StatusMethodNotAllowed"))
			return
		}
		reqData, err := io.ReadAll(r.Body)
		if err != nil {
			w.WriteHeader(http.StatusBadRequest)
			_, _ = w.Write([]byte("StatusBadRequest"))
			return
		}
		req := new(GmCmdReq)
		err = json.Unmarshal(reqData, req)
		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			_, _ = w.Write([]byte("StatusInternalServerError"))
			return
		}
		GmCmdReqChan <- req
		rsp := <-GmCmdRspChan
		rspData, _ := json.Marshal(rsp)
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write(rspData)
	})
	err = http.ListenAndServe(addr, nil)
	if err != nil {
		return err
	}
	return nil
}

type GmCmdReq struct {
	Uid       uint32   `json:"uid"`
	FuncName  string   `json:"func_name"`
	ParamList []string `json:"param_list"`
}

type GmCmdRsp struct {
	Ok  bool   `json:"ok"`
	Msg string `json:"msg"`
}

var GmCmdReqChan = make(chan *GmCmdReq, 1000)
var GmCmdRspChan = make(chan *GmCmdRsp, 1000)
